﻿using System;
using System.Threading;
using System.Windows.Input;

using NanoMvvm.Properties;

namespace NanoMvvm
{
    /// <summary>
    /// A base class for command implementations.
    /// </summary>
    public abstract class CommandBase : ICommand
    {
        private readonly WeakEventContainer<object, EventArgs> canExecuteChangedEventContainer;
        private readonly SynchronizationContext synchronizationContext;
        private bool isAutomaticRequeryDisabled;

        protected CommandBase(bool automaticRequery = true)
            : this(NanoMvvmFramework.Instance.Synchronization.Main, automaticRequery)
        {
        }

        protected CommandBase(SynchronizationContext context, bool automaticRequery = true)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context", Resources.SyncContextNotProvided);
            }

            canExecuteChangedEventContainer = new WeakEventContainer<object, EventArgs>();
            synchronizationContext = context;
            isAutomaticRequeryDisabled = !automaticRequery;
        }

        event EventHandler ICommand.CanExecuteChanged
        {
            add
            {
                if (!IsAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested += value;
                }

                OnCanExecuteChangedAddEventHandler(value);
            }

            remove
            {
                if (!IsAutomaticRequeryDisabled)
                {
                    CommandManager.RequerySuggested -= value;
                }

                OnCanExecuteChangedRemoveEventHandler(value);
            }
        }

        public virtual SynchronizationContext SynchronizationContext
        {
            get { return synchronizationContext; }
        }

        public virtual bool IsAutomaticRequeryDisabled
        {
            get
            {
                return isAutomaticRequeryDisabled;
            }

            set
            {
                if (isAutomaticRequeryDisabled != value)
                {
                    if (value)
                    {
                        AddWeakHandlersToCommandManager();
                    }
                    else
                    {
                        RemoveWeakHandlersFromCommandManager();
                    }
                    isAutomaticRequeryDisabled = value;

                    OnCanExecuteChanged(this);
                }
            }
        }

        internal Func<bool> CanExecuteCondition { get; set; } 

        protected internal virtual void OnCanExecuteChanged(ICommand sender)
        {
            if (SynchronizationContext != null)
            {
                SynchronizationContext.Send(state => OnCanExecuteChangedImpl((ICommand)state), sender);
            }
            else
            {
                OnCanExecuteChangedImpl(sender);
            }
        }

        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged(this);
        }

        protected abstract void Execute(object parameter);

        protected abstract bool CanExecute(object parameter);

        protected virtual void OnCanExecuteChangedAddEventHandler(EventHandler handler)
        {
            canExecuteChangedEventContainer.AddHandler(new Action<object, EventArgs>(handler));
        }

        protected virtual void OnCanExecuteChangedRemoveEventHandler(EventHandler handler)
        {
            canExecuteChangedEventContainer.RemoveHandler(new Action<object, EventArgs>(handler));
        }

        private void AddWeakHandlersToCommandManager()
        {
            foreach (var handler in canExecuteChangedEventContainer.GetHandlers())
            {
                CommandManager.RequerySuggested += new EventHandler(handler);
            }
        }

        private void RemoveWeakHandlersFromCommandManager()
        {
            foreach (var handler in canExecuteChangedEventContainer.GetHandlers())
            {
                CommandManager.RequerySuggested -= new EventHandler(handler);
            }
        }

        void ICommand.Execute(object parameter)
        {
            // consider: useful check?
            if (CanExecute(parameter))
            {
                Execute(parameter);
            }
        }

        bool ICommand.CanExecute(object parameter)
        {
            return CanExecuteCondition != null ? CanExecuteCondition() && CanExecute(parameter) : CanExecute(parameter);
        }

        private void OnCanExecuteChangedImpl(ICommand sender)
        {
            if (!IsAutomaticRequeryDisabled)
            {
                CommandManager.InvalidateRequerySuggested();
            }

            canExecuteChangedEventContainer.InvokeAll(sender, EventArgs.Empty);
        }
    }
}